# -*- encoding: UTF-8 -*-
'''
Created on 11 mars 2011

@author: thierry
'''
import os
import time
from threading import Timer
import urllib
import datetime
import subprocess

import logging

from sets import Set

import vobject

import evolution.ebook

from xml.dom.minidom import parseString
import dbus

import location   
import glib, gobject

import mutex

import gst




LOCAL_UPLOAD_DIR = "/home/user/MyDocs/.documents"

BATTERY_CMD = "hal-device bme | awk '/l.p/ {perc = $3}; /g.c/ {curr = $3}; /g.d/ {last = $3}; /s_c/ {isch = $3} END if (isch == \"false\") {print perc\" % (\"curr\"/\"last\" mAh)\"} else {print \"Charging\"}'"
UPTIME_CMD = "uptime | sed -e 's/.*p *//' -e 's/, l.*//' -e 's/  / /'"
DATE_CMD = "date +\"%A %d %B %Y\""
TIME_CMD = "date +\"%H:%M\""
PROFILE_CMD = "dbus-send --type=method_call --print-reply --dest=com.nokia.profiled /com/nokia/profiled com.nokia.profiled.get_profile | awk '/string/ {prof = $2} END {print prof}'"
SHUTDOWN_CMD = "dbus-send --system --type=method_call --print-reply --dest=com.nokia.mce /com/nokia/mce/request com.nokia.mce.request.req_shutdown"
OPEN_URL_CMD = "dbus-send --system --type=method_call --dest=com.nokia.osso_browser /com/nokia/osso_browser/request com.nokia.osso_browser.load_url string:\"%s\""
UNLOCK_DEVICE_CMD = "dbus-send --system --type=method_call --dest=com.nokia.system_ui /com/nokia/system_ui/request com.nokia.system_ui.request.devlock_close string:\"com.nokia.mce\" string:\"/com/nokia/mce/request\" string:\"com.nokia.mce.request\" string:\"devlock_callback\" uint32:'0'"
LOCK_DEVICE_CMD = "dbus-send --system --type=method_call --dest=com.nokia.system_ui /com/nokia/system_ui/request com.nokia.system_ui.request.devlock_open string:\"com.nokia.mce\" string:\"/com/nokia/mce/request\" string:\"com.nokia.mce.request\" string:\"devlock_callback\" uint32:'3'"



class Web900WebAPI(urllib.FancyURLopener):    
    """
    This object know how to speak with the web900 server.
    Warning: not thread safe
    """
   
    
    def __init__(self, url, user, password, key):
        urllib.FancyURLopener.__init__(self)
        self.url = url
        self.user = user
        self.password = password
        self.key = key
        
    def prompt_user_passwd(self, host, realm):
        return (self.user, self.password)
    
    def push_status(self, **kwargs):     
        copy = kwargs.copy()
        copy['key'] = self.key
        params = urllib.urlencode(copy)           
        f = self.open(self.url + "/action.py/push_status" , params)
        f.close() 

    def poll_command(self):        
        params = urllib.urlencode({'key': self.key})
        f = self.open(self.url + "/action.py/poll_command", params) 
        return f.read()
    
    def push_addressBook(self, **kwargs):
        copy = kwargs.copy()
        copy['key'] = self.key
        params = urllib.urlencode(copy)
        f = self.open(self.url + "/action.py/push_address_book", params)
        f.close()
        
    def download(self, filename, filekey):
        save_as_file = os.path.join(LOCAL_UPLOAD_DIR, filename)        
        filename, headers = self.retrieve(self.url + ("/upload/%s" % (urllib.quote(filekey))),
                                             save_as_file)
        params = urllib.urlencode({ 'filename':filename, 'key': self.key, 'filekey':filekey})
        f = self.open(self.url + "/action.py/delete_uploaded_file", params)
        f.close()
        
    def push_location(self, lat, long, fixTime):        
        params = urllib.urlencode({'lat': lat, 'long' : long, 'fix' : fixTime, 'key': self.key})
        f = self.open(self.url + "/action.py/push_location", params)
        f.close()
        
    def read_signal(self):
        params = urllib.urlencode({'key': self.key})
        f = self.open(self.url + "/action.py/add_cmd_listener", params)
        while True:
            octet = f.read(1)
            yield octet
             


class LocationPusher():
    """
    Send GPS location
    """        
    done = False  
    
    def __init__(self, api, once):
        self.api = api               
        self.once = once
        self.thread = Timer(0, self.run)
        self.thread.start()
    
    
    def stop_request(self):        
        return self.once and self.done
    

    def update_location(self):
        loop = gobject.MainLoop()
        logging.debug("getting gps")
        control = location.GPSDControl.get_default()
        logging.debug("gps control ok")
        device = location.GPSDevice()
        logging.debug("gps device ok")
        control.set_properties(preferred_method=location.METHOD_USER_SELECTED, preferred_interval=location.INTERVAL_DEFAULT)
        logging.debug("gps properties ok")
        control.connect("error-verbose", self.on_error, loop)
        device.connect("changed", self.on_changed, control)
        control.connect("gpsd-stopped", self.on_stop, loop)
        gobject.idle_add(self.start_location, control)
        loop.run()

    def run(self):
        """
        Callback function to bring this object to life
        """        
        while not self.stop_request():
                           
            self.update_location()      
            self.done = True
            if not self.once:                 
                time.sleep(60 * 60)
            


    def on_error(self, control, error, data):        
        logging.error("location error: %d... quitting" % error)
        data.quit()

    def on_stop(self, control, data):
        logging.debug("gps quitting")
        data.quit()


 
    def on_changed(self, device, data):
        if not device:
            return
        if device.fix:
            if (device.fix[1] & location.GPS_DEVICE_LATLONG_SET) and (device.fix[1] & location.GPS_DEVICE_TIME_SET):
                
                latlong = device.fix[4:6]
                fixtime = device.fix[2]
                logging.info("lat = %f, long = %f, fix=%f" % (latlong[0], latlong[1], fixtime))
                
                if time.time() - fixtime < 120 :           
                    self.api.push_location(latlong [0], latlong [1], fixtime)                
                    data.stop()
 

 
    def start_location(self, data):
        logging.debug("gps starting")       
        data.start()
        return False
 

 


class StatusPusher():
    """
    This object push the status of the device into to web900 webapp.
    """
    
    done = False
    def __init__(self, api, once):
        self.api = api
        self.once = once        
        self.thread = Timer(0, self.run)
        self.thread.start()
    
    
    def stop_request(self):
        return self.once and self.done
    
    def run(self):
        """
        Callback function to bring this object to life
        """        
        while not self.stop_request():
            p = subprocess.Popen([BATTERY_CMD], shell=True, stdout=subprocess.PIPE)
            battery, stderr = p.communicate()      
        
            p = subprocess.Popen([UPTIME_CMD], shell=True, stdout=subprocess.PIPE)
            uptime, stderr = p.communicate()    
            
            p = subprocess.Popen([DATE_CMD], shell=True, stdout=subprocess.PIPE)
            theDate, stderr = p.communicate()      
            
            p = subprocess.Popen([TIME_CMD], shell=True, stdout=subprocess.PIPE)
            theTime, stderr = p.communicate()      
            
            p = subprocess.Popen([PROFILE_CMD], shell=True, stdout=subprocess.PIPE)
            profile, stderr = p.communicate() 
                
            status = {'time': theTime,
                      'date' : theDate,
                      'battery' : battery,
                      'uptime' : uptime,
                      'profile' :  profile
                     }
            try:
                self.api.push_status(**status)
                self.done = True
            except IOError:
                pass
            if not self.once:
                time.sleep(30)

class RealTimeCommandPooler():
    """
    This object pool the web900 webapp for command then execute them 
    """
    
    done = False
    
    def __init__(self, api, cp, once):
        self.api = api
        self.cp = cp
        self.once = once
        self.thread = Timer(0, self.run)
        self.thread.start()

    def stop_request(self):
        return self.once and self.done

    def run(self):
        """
        Callback function to bring this object to life
        """        
        while not self.stop_request():
            try:
                for signal in  self.api.read_signal():
                    if signal == '0':
                        pass
                    else :                    
                        logging.info("received real time signal '%s'" % signal)
                        self.cp.run_synchronized()    
                self.done = True                                                    
            except IOError:
                pass        
            if not self.once:    
                time.sleep(1)
    
class CommandPooler():
    """
    This object pool the web900 webapp for command then execute them 
    """
    
    done = False
    
    def __init__(self, api, lp, once):
        self.api = api
        self.location = lp
        self.once = once
        self.poll_mutex = mutex.mutex()
        self.thread = Timer(0, self.run)
        self.thread.start()

    def stop_request(self):
        return self.once and self.done
    
    
    def _octify(self, str):
        '''     
        Returns a list of octet bytes representing
        each char of the input str.               
        '''                                       
 
        bytes = map(ord, str)
        bitsconsumed = 0     
        referencebit = 7     
        octets = []          
 
        while len(bytes):
                byte = bytes.pop(0)
                byte = byte >> bitsconsumed
 
                try:                       
                        nextbyte = bytes[0]
                        bitstocopy = (nextbyte & (0xff >> referencebit)) << referencebit
                        octet = (byte | bitstocopy)                                     
 
                except:
                        octet = (byte | 0x00)
 
                if bitsconsumed != 7:
                        octets.append(byte | bitstocopy)
                        bitsconsumed += 1               
                        referencebit -= 1               
                else:                                   
                        bitsconsumed = 0                
                        referencebit = 7                
 
        return octets
 
    def _semi_octify(self, str):
        '''          
        Expects a string containing two digits.
        Returns an octet -                     
        first nibble in the octect is the first
        digit and the second nibble represents 
        the second digit.                      
        '''                                    
        try:                                   
                digit_1 = int(str[0])          
                digit_2 = int(str[1])          
                octet = (digit_2 << 4) | digit_1
        except:                                 
                octet = (1 << 4) | digit_1      
 
        return octet
 

    
    def _createPDUmessage(self, number, msg):
        '''                       
        Returns a list of bytes to represent a valid PDU message
        '''                                                     
        numlength = len(number)                                 
        if (numlength % 2) == 0:                                
                rangelength = numlength                         
        else:                                                   
                number = number + 'F'                           
                rangelength = len(number)                       
 
        octifiednumber = [ self._semi_octify(number[i:i + 2]) for i in range(0, rangelength, 2) ]
        octifiedmsg = self._octify(msg)                                                      
        HEADER = 1                                                                     
        FIRSTOCTETOFSMSDELIVERMSG = 10                                                 
        ADDR_TYPE = 129 #unknown format                                                
        number_length = len(number)                                                    
        msg_length = len(msg)                                                          
        pdu_message = [HEADER, FIRSTOCTETOFSMSDELIVERMSG, number_length, ADDR_TYPE]    
        pdu_message.extend(octifiednumber)                                             
        pdu_message.append(0)                                                          
        pdu_message.append(0)                                                          
        pdu_message.append(msg_length)                                                 
        pdu_message.extend(octifiedmsg)                                                
        return pdu_message                                                             
 
 
    def _sendmessage(self, number, message):
 
        bus = dbus.SystemBus()
        smsobject = bus.get_object('com.nokia.phone.SMS', '/com/nokia/phone/SMS/ba212ae1')
        smsiface = dbus.Interface(smsobject, 'com.nokia.csd.SMS.Outgoing')
        arr = dbus.Array(self._createPDUmessage(number.replace('+', '00'), message))
 
        msg = dbus.Array([arr])
        smsiface.Send(msg, '')
 

    
    def execute_sms(self, command):
        recipient = command.getElementsByTagName("recipient")[0].firstChild.nodeValue
        content = command.getElementsByTagName("content")[0].firstChild.nodeValue
        self._sendmessage(recipient, content)
        bus = dbus.SessionBus()
        proxy = bus.get_object('org.freedesktop.Notifications', '/org/freedesktop/Notifications')
        interface = dbus.Interface(proxy, dbus_interface='org.freedesktop.Notifications')
        interface.Notify('Notification', 0, 'control_bluetooth_paired', 'Web900', "sms sent", [], {}, 2000)

    def execute_im(self, command):
        login = command.getElementsByTagName("login")[0].firstChild.nodeValue
        name = command.getElementsByTagName("name")[0].firstChild.nodeValue
        if name != "" :
            sender = name
        elif login != "":
            sender = login
        else:
            logging.error("received im with no sender, discard notification")
        content = command.getElementsByTagName("content")[0].firstChild.nodeValue       
        bus = dbus.SessionBus()
        proxy = bus.get_object('org.freedesktop.Notifications', '/org/freedesktop/Notifications')
        interface = dbus.Interface(proxy, dbus_interface='org.freedesktop.Notifications')
        interface.Notify('Notification', 0, 'control_bluetooth_paired', 'Web900', "%s:\n %s" % (sender, content), [], {}, 2000)

        
    def execute_upload(self, command):
        filename = command.getElementsByTagName("filename")[0].firstChild.nodeValue
        filekey = command.getElementsByTagName("filekey")[0].firstChild.nodeValue          
        downloaded = self.api.download(filename, filekey)
        bus = dbus.SessionBus()
        proxy = bus.get_object('org.freedesktop.Notifications', '/org/freedesktop/Notifications')
        interface = dbus.Interface(proxy, dbus_interface='org.freedesktop.Notifications')
        interface.Notify('Notification', 0, 'control_bluetooth_paired', 'Web900', "File received", [], {}, 2000)

     
    def execute_call(self, command):
        number = command.getElementsByTagName("number")[0].firstChild.nodeValue
        bus = dbus.SystemBus()
        csd_call = dbus.Interface(bus.get_object('com.nokia.csd',
                                           '/com/nokia/csd/call'),
                                           'com.nokia.csd.Call')
        csd_call.CreateWith(str(number), dbus.UInt32(0))               

    def execute_url(self, command):
        url = command.getElementsByTagName("url")[0].firstChild.nodeValue
        subprocess.Popen([OPEN_URL_CMD % url], shell=True)
        
    def execute_shutdown(self):
        subprocess.Popen([SHUTDOWN_CMD], shell=True)
    
    def execute_lock(self):
        subprocess.Popen([LOCK_DEVICE_CMD], shell=True)
    
    def execute_unlock(self):
        subprocess.Popen([UNLOCK_DEVICE_CMD], shell=True)
    
    def execute_location(self):
        self.location.update_location()

    def execute_ring(self):
        # prepare the player
        
        
        class RingPlayer():
            def __init__(self):
                self.player = gst.element_factory_make("playbin2", "player")
                fakesink = gst.element_factory_make("fakesink", "fakesink")
                self.player.set_property("video-sink", fakesink)
                bus = self.player.get_bus()
                bus.add_signal_watch()        
                bus.connect("message", self.on_message)

            def on_message(self, bus, message):
                t = message.type
                if t == gst.MESSAGE_EOS:
                    self.player.set_state(gst.STATE_NULL)
                    self.playmode = False
                elif t == gst.MESSAGE_ERROR:
                    self.player.set_state(gst.STATE_NULL)
                    err, debug = message.parse_error()
                    print "Error: %s" % err, debug
                    self.playmode = False

            def start(self):
                self.playmode = True
                mediafile = "file:///home/user/MyDocs/.sounds/Ringtones/Lumina.aac"
                self.player.set_property("uri", mediafile);
                self.player.set_state(gst.STATE_PLAYING)
                logging.debug("playing %s" % mediafile)
                while self.playmode:
                    time.sleep(1)
                time.sleep(1)
                loop.quit()

        player = RingPlayer()
        thread = Timer(0, player.start)
        thread.start()             
        loop = glib.MainLoop()
        loop.run()


        


    def run_synchronized(self):
        self.poll_mutex.lock(self._run, None)
        self.poll_mutex.unlock()                    

    def _run(self, data=None):        
            try:
                bulk = self.api.poll_command()
                logging.debug(bulk)
            except IOError:
                pass
            else:
                dom = parseString(bulk)
                for command in dom.getElementsByTagName("command"):
                    command_id = command.getAttribute("id")
                    if command_id.startswith("sms"):
                        self.execute_sms(command)
                    elif command_id == "shutdown":
                        self.execute_shutdown()
                    elif command_id.startswith("upload"):
                        self.execute_upload(command)
                    elif command_id == "call":
                        self.execute_call(command)
                    elif command_id.startswith("url"):
                        self.execute_url(command)
                    elif command_id == "lock":
                        self.execute_lock()
                    elif command_id == "unlock" :
                        self.execute_unlock()
                    elif command_id == "location" :
                        self.execute_location()
                    elif command_id == "im":
                        self.execute_im(command)
                    elif command_id == "ring":
                        self.execute_ring()
                    
                    
    def run(self):
        """
        Callback function to bring this object to life
        """        
        while not self.stop_request():
            self.run_synchronized()
            self.done = True
            if not self.once:
                time.sleep(30)


class AdressBookPusher():
    """
    This class is responsible for publishing the device address book
    """
    rel_phone_map = [(Set(['CELL', 'VOICE']), 'mobile-phone'),
                           (Set(['HOME', 'CELL']), 'home-phone-2'),
                           (Set(['WORK', 'CELL']), 'business-phone-2'),
                           (Set(['VOICE']), 'other-phone'),
                           (Set(['HOME', 'VOICE']), 'home-phone'),
                           (Set(['WORK', 'VOICE']), 'business-phone'),
                           (Set(['FAX']), 'other-fax-phone')                           
                           ]  
            
    rel_mail_map = {'WORK': "WORK",
                    'HOME': "HOME",
                           }     
    done = False
    
    def __init__(self, api, once):
        self.api = api
        self.once = once
        self.thread = Timer(0, self.run)
        self.thread.start()

    def stop_request(self):
        return self.once and self.done
    
    def run(self):
        """
        Callback function to bring this object to life
        """        
        while not self.stop_request():          
            contacts = self.getAllLocalContacts()
            for contact in contacts:               
                data = self.extractContactData(contact)  
                try:                          
                    self.api.push_addressBook(**data)
                except IOError:
                    pass
            self.done = True
            if not self.once:
                time.sleep(60 * 60)

    def commitContacts(self):
        """
        Upload local contacts into google address book
        """


    def getAllLocalContacts(self, reload=False):
        """
        Return the all local contact
        """
        abook = evolution.ebook.open_addressbook("default")
        self.localContacts = abook.get_all_contacts()
        return self.localContacts
    
    def escapeNonAsciiChar(self, value):
        resu = value
        try:
            resu = value.encode('ascii', 'xmlcharrefreplace')
        except UnicodeDecodeError:
            resu = self.escapeNonAsciiChar(unicode(value, "utf"))
        return resu
    
    def extractContactData(self, econtact):
        """
        read all suitable value from econtact and write them in google entry
        """
        data = {}
        data['uid'] = econtact.get_uid()
        if  econtact.get_name():
            data['name'] = self.escapeNonAsciiChar(econtact.get_name())
           
        vcard = econtact.get_vcard_string()
        v = vobject.readOne(vcard)             
        
        try:
            logging.debug("note: %s" % v.note.value)
        except AttributeError:
            pass        
        else:
            if v.note.value:
                data['note'] = self.escapeNonAsciiChar(v.note.value)
                
        try:
            logging.debug("nickname: %s" % v.nickname.value)
        except AttributeError:
            pass
        else:    
            if v.nickname.value:
                data['nickname'] = self.escapeNonAsciiChar(v.nickname.value)
        
        try:
            logging.debug("url: %s" % v.url)
        except AttributeError:
            pass
        else:
            if v.url.value:                
                data['url'] = self.escapeNonAsciiChar(v.url.value)
        
        try:
            logging.debug("birthday: %s" % v.bday.value)
        except AttributeError:
            pass
        else:
            if v.bday.value:                                    
                data['birthday'] = self.escapeNonAsciiChar(v.bday.value)
            
        try:    
            logging.debug("org: %s" % v.org.value)
        except AttributeError:
            pass
        else:            
            data['organization'] = self.escapeNonAsciiChar(v.org.value[0])                      
        
        try:
            logging.debug("title: %s" % v.title.value)
        except AttributeError:
            pass
        else:
            data['title'] = self.escapeNonAsciiChar(v.title.value)
        
        
        try:
            v.email_list
        except AttributeError:
            pass
        else:
            for email in v.email_list:            
                address = email.value                
                rel_value = None
                try:
                    types = email.type_param
                except AttributeError:
                    types = ""                
                else:                                            
                    if types in self.rel_mail_map.keys():
                        rel_value = self.rel_mail_map[types]
                    else:
                        rel_value = "other"            
                            
                logging.debug("email[%s] : %s" % (types, address))
                if rel_value:
                    data['email-' + rel_value] = self.escapeNonAsciiChar(address)                                  
                else:
                    data['email'] = self.escapeNonAsciiChar(address)
                
     
        try:
            v.tel_list
        except AttributeError:
            pass
        else:
            for tel in v.tel_list:            
                phone = tel.value                 
                rel_value = None
                try:
                    types = tel.type_paramlist
                except AttributeError:
                    types = ""                
                else:                
                    types_set = Set(types)
                    for paramset, evo_prop in self.rel_phone_map:
                        if len(paramset.symmetric_difference(types_set)) == 0 :
                            rel_value = evo_prop
                            break                
                    else:
                        rel_value = "other"        
                logging.debug("phone[%s] : %s" % (types, phone))
                if rel_value:
                    data['phone-' + rel_value] = self.escapeNonAsciiChar(phone)
                else:
                    data['phone'] = self.escapeNonAsciiChar(phone)
                                                   
        

        return data
    
        
    

def run(server, login, password, key, once):
    """
    launch the web900 app device part.
    """        
    
    #iinstanciate a status pusher
    STATUS_PUSHER = StatusPusher(Web900WebAPI(server, login , password, key), once)
      # instanciate the location pusher
    LOCATION_PUSHER = LocationPusher(Web900WebAPI(server, login, password, key), once)
    # instanciate a command pooler
    COMMAND_POOLER = CommandPooler(Web900WebAPI(server, login , password, key), LOCATION_PUSHER, once)
    # instanciate an address book pusher
    ADDRESS_BOOK_PUSHER = AdressBookPusher(Web900WebAPI(server, login, password, key), once)        
    # real time signal listener
    #REAL_TIME_LISTNER = RealTimeCommandPooler(Web900WebAPI(server, login, password), COMMAND_POOLER)
  
    
    
    STATUS_PUSHER.thread.join()
    COMMAND_POOLER.thread.join()
    ADDRESS_BOOK_PUSHER.thread.join()
    LOCATION_PUSHER.thread.join()

    
    
